Passed
Pull Request — master (#133)
by Jan
01:31
created

ID3Frame.getValue   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
1
const zlib = require('zlib')
2
const ID3FrameHeader = require('./ID3FrameHeader')
3
const ID3Frames = require('./ID3Frames')
4
const ID3Util = require('./ID3Util')
5
6
class ID3Frame {
7
    constructor(identifier, value, flags = {}) {
8
        this.identifier = identifier
9
        this.value = value
10
        this.dataLengthIndicator = 0
11
        this.flags = flags
12
    }
13
14
    static createFromBuffer(frameBuffer, version) {
15
        const frameHeaderSize = ID3FrameHeader.getSizeByVersion(version)
16
        // Specification requirement
17
        if(frameBuffer < frameHeaderSize + 1) {
18
            return null
19
        }
20
        const frameHeaderBuffer = frameBuffer.subarray(0, frameHeaderSize)
21
        const frameHeader = ID3FrameHeader.createFromBuffer(frameHeaderBuffer, version)
22
        if(frameHeader.flags.encryption) {
23
            return null
24
        }
25
26
        const frameBodyOffset = frameHeader.flags.dataLengthIndicator ? 4 : 0
27
        const frameBodyStart = frameHeaderSize + frameBodyOffset
28
        let frameBody = frameBuffer.subarray(frameBodyStart, frameBodyStart + frameHeader.frameSize - frameBodyOffset)
29
        if(frameHeader.flags.unsynchronisation) {
30
            // This method should stay in ID3Util for now because it's also used in the Tag's header which we don't have a class for.
31
            frameBody = ID3Util.processUnsynchronisedBuffer(frameBody)
32
        }
33
        if(frameHeader.flags.dataLengthIndicator) {
34
            this.dataLengthIndicator = frameBuffer.readInt32BE(frameHeaderSize)
35
        }
36
        if(frameHeader.flags.compression) {
37
            const uncompressedFrameBody = this.decompressBodyBuffer(frameBody, this.dataLengthIndicator)
38
            if(!uncompressedFrameBody) {
39
                return null
40
            }
41
            frameBody = uncompressedFrameBody
42
        }
43
44
        let value = null
0 ignored issues
show
Unused Code introduced by
The assignment to value seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
45
        if(ID3Frames[frameHeader.identifier]) {
46
            value = ID3Frames[frameHeader.identifier].read(frameBody, version)
47
        } else if(frameHeader.identifier.startsWith('T')) {
48
            value = ID3Frames.GENERIC_TEXT.read(frameBody, version)
49
        } else if(frameHeader.identifier.startsWith('W')) {
50
            value = ID3Frames.GENERIC_URL.read(frameBody, version)
51
        } else {
52
            // Unknown frames are not supported currently.
53
            return null
54
        }
55
56
        return new ID3Frame(frameHeader.identifier, value, frameHeader.flags)
57
    }
58
59
    getBuffer() {
60
        if(ID3Frames[this.identifier]) {
61
            return ID3Frames[this.identifier].create(this.value)
62
        }
63
        if(this.identifier.startsWith('T')) {
64
            return ID3Frames.GENERIC_TEXT.create(this.value)
65
        }
66
        if(this.identifier.startsWith('W')) {
67
            return ID3Frames.GENERIC_URL.create(this.value)
68
        }
69
70
        return null
71
    }
72
73
    getValue() {
74
        return this.value
75
    }
76
77
    static decompressBodyBuffer(bodyBuffer, dataLengthIndicator) {
78
        if(bodyBuffer.length < 5 || dataLengthIndicator === undefined) {
79
            return null
80
        }
81
82
        /*
83
        * ID3 spec defines that compression is stored in ZLIB format, but doesn't specify if header is present or not.
84
        * ZLIB has a 2-byte header.
85
        * 1. try if header + body decompression
86
        * 2. else try if header is not stored (assume that all content is deflated "body")
87
        * 3. else try if inflation works if the header is omitted (implementation dependent)
88
        * */
89
        let decompressedBody
90
        try {
91
            decompressedBody = zlib.inflateSync(bodyBuffer)
92
        } catch (e) {
93
            try {
94
                decompressedBody = zlib.inflateRawSync(bodyBuffer)
95
            } catch (e) {
96
                try {
97
                    decompressedBody = zlib.inflateRawSync(bodyBuffer.subarray(2))
98
                } catch (e) {
99
                    return null
100
                }
101
            }
102
        }
103
        if(decompressedBody.length !== dataLengthIndicator) {
104
            return null
105
        }
106
        return decompressedBody
107
    }
108
}
109
110
module.exports = ID3Frame